﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using betfairfind.Betfair.UK;

namespace betfairfind.Rules
{
    public class CustomRule
    {
        public void Test(List<MyMarket> inplaymarkets, Dictionary<long, MarketPricesCompressed> marketprices)
        {
            var prices = from market in inplaymarkets
                         let mprices = marketprices[market.marketId]
                         select new Tuple<MyMarket, MarketPricesCompressed>(market, mprices);

            var strats = ConvertRulesToStrategies();

            foreach (var strategy in strats.Strategies.Where(s => s.IsEnabled))
            {
                Logger.Log("============================================================================", true, true);
                Logger.Log("Matches qualified for " + strategy.Name, true, true);
                Logger.Log("----------------------------------------------------------------------------", true, true);
                
                foreach (var market in strategy.Filter(prices))
                {
                    Logger.Log(market.marketTime + ", " + market.eventHierarchy + market.name, true, true);
                }
            }

            XmlSerializer serializer = new XmlSerializer(typeof(StrategyCollection));
            TextWriter textWriter = new StreamWriter(@"rules.xml");
            serializer.Serialize(textWriter, strats);
            textWriter.Close();
        }

        private static StrategyCollection ConvertRulesToStrategies()
        {
            StrategyFilter showcase = new StrategyFilter
            {
                Name = "Showcase",
                IsEnabled = false,
                Markets = new List<MarketFilter>
                {
                    new MarketFilter { Name = "Match Odds", Liquidity = new LiquidityFilter { Currency = "USD", ExchangeRate = 1.557, MinMatched = 10000}, Selections = new List<SelectionFilter> {
                        new SelectionFilter { ID = 1 },
                        new SelectionFilter { Index = 2 },
                        new SelectionFilter { Name = "The Draw", BestPrice = new BestPriceFilter{ Type = PriceType.Back, MinOdds = 1.01, MaxOdds = 1000, MinUnmatched = 1000 }, NoGaps = new NoGapsFilter() } },
                    
                    BestSelections = new List<BestSelectionFilter>{
                        new BestSelectionFilter { BestPrice = new BestPriceFilter { MinOdds = 1.5, Type = PriceType.Lay } },
                        new BestSelectionFilter { Index = 2, BestPrice = new BestPriceFilter { MinOdds = 3, Type = PriceType.Lay } }
                    }}
                }
            };

            StrategyFilter ltd = new StrategyFilter { Name = "Lay the Draw" };
            MarketFilter matchOdds = new MarketFilter { Name = "Match Odds" };
            matchOdds.Liquidity = new LiquidityFilter { MinMatched = 500 };
            matchOdds.Selections = new List<SelectionFilter>();
            matchOdds.Selections.Add(new SelectionFilter { Index = 1, BestPrice = new BestPriceFilter { MinOdds = 1, MaxOdds = 2, Type = PriceType.Back } });
            matchOdds.Selections.Add(new SelectionFilter { Index = 2, BestPrice = new BestPriceFilter { MinOdds = 2, MaxOdds = 10, Type = PriceType.Back } });
            matchOdds.Selections.Add(new SelectionFilter { Name = "The Draw", BestPrice = new BestPriceFilter { MinOdds = 1, MaxOdds = 4.6, Type = PriceType.Back } });
            MarketFilter uo25goals = new MarketFilter { Name = "Over/Under 2.5 Goals" };
            uo25goals.Selections = new List<SelectionFilter>();
            uo25goals.Selections.Add(new SelectionFilter { Name = "Over 2.5 Goals", BestPrice = new BestPriceFilter { Type = PriceType.Back, MaxOdds = 2 } });
            ltd.Markets = new List<MarketFilter> { matchOdds, uo25goals };

            StrategyFilter ltd2 = new StrategyFilter { Name = "Lay the Draw 2" };
            MarketFilter matchOdds2 = new MarketFilter { Name = "Match Odds" };
            matchOdds2.Liquidity = new LiquidityFilter { MinMatched = 500 };
            matchOdds2.Selections = new List<SelectionFilter>();
            matchOdds2.Selections.Add(new SelectionFilter { Index = 1, BestPrice = new BestPriceFilter { MinOdds = 2, MaxOdds = 10, Type = PriceType.Back } });
            matchOdds2.Selections.Add(new SelectionFilter { Index = 2, BestPrice = new BestPriceFilter { MinOdds = 1, MaxOdds = 1.95, Type = PriceType.Back } });
            matchOdds2.Selections.Add(new SelectionFilter { Name = "The Draw", BestPrice = new BestPriceFilter { MinOdds = 1, MaxOdds = 4.6, Type = PriceType.Back } });
            MarketFilter uo25goals2 = new MarketFilter { Name = "Over/Under 2.5 Goals" };
            uo25goals2.Selections = new List<SelectionFilter>();
            uo25goals2.Selections.Add(new SelectionFilter { Name = "Over 2.5 Goals", BestPrice = new BestPriceFilter { Type = PriceType.Back, MaxOdds = 1.95 } });
            ltd2.Markets = new List<MarketFilter> { matchOdds2, uo25goals2 };

            StrategyFilter over15 = new StrategyFilter { Name = "Over 1.5", IsEnabled = false };
            MarketFilter over15g = new MarketFilter { Name = "Over/Under 1.5 Goals" };
            over15g.Liquidity = new LiquidityFilter { MinMatched = 500 };
            over15g.Selections = new List<SelectionFilter> { new SelectionFilter { Name = "Over 1.5 Goals", BestPrice = new BestPriceFilter { Type = PriceType.Back, MaxOdds = 2, MinOdds = 1 } } };
            over15.Markets = new List<MarketFilter> { over15g };

            StrategyFilter over25 = new StrategyFilter { Name = "Trading O1.5 based on O2.5" };
            MarketFilter over25g = new MarketFilter { Name = "Over/Under 2.5 Goals" };
            over25g.Liquidity = new LiquidityFilter { MinMatched = 500 };
            over25g.Selections = new List<SelectionFilter> { new SelectionFilter { Name = "Over 2.5 Goals", BestPrice = new BestPriceFilter { Type = PriceType.Back, MaxOdds = 2, MinOdds = 1 } } };
            MarketFilter over15g25 = new MarketFilter { Name = "Over/Under 1.5 Goals" };
            over15g25.Liquidity = new LiquidityFilter { MinMatched = 500 };
            over25.Markets = new List<MarketFilter> { over25g, over15g25 };

            StrategyFilter cs001221 = new StrategyFilter
            {
                Name = "Correct Score 0-0, 2-1, 1-2",
                Markets = new List<MarketFilter>
                {
                    new MarketFilter { Name = "Correct Score", Liquidity = new LiquidityFilter { MinMatched = 500 }, Selections = new List<SelectionFilter> {
                        new SelectionFilter { Index = 1, BestPrice = new BestPriceFilter { MinOdds = 2, MaxOdds = 4, Type = PriceType.Back } },
                        new SelectionFilter { Index = 2, BestPrice = new BestPriceFilter { MinOdds = 2, MaxOdds = 4, Type = PriceType.Back } } }, },
                    new MarketFilter { Name = "Over/Under 2.5 Goals", Selections = new List<SelectionFilter> { 
                        new SelectionFilter { Name = "Over 2.5 Goals", BestPrice = new BestPriceFilter { Type = PriceType.Back, MaxOdds = 2 } } } }
                }
            };

            StrategyFilter cs0011 = new StrategyFilter
            {
                Name = "Correct Score 0-0, 1-1",
                IsEnabled = false,
                Markets = new List<MarketFilter> { new MarketFilter { Name = "Correct Score", Liquidity = new LiquidityFilter { MinMatched = 500 }, Selections = new List<SelectionFilter> {
                    new SelectionFilter { Name = "0-0", BestPrice = new BestPriceFilter { Type = PriceType.Back, MinOdds = 12 } },
                    new SelectionFilter { Name = "1-1", BestPrice = new BestPriceFilter { Type = PriceType.Back, MinOdds = 7.2 } } } }
                }
            };
            // TODO: ticks

            StrategyFilter o25ht00ft00 = new StrategyFilter
            {
                Name = "Correct Score 0-0, HT 0-0, Over 2.5", IsEnabled = false,
                Markets = new List<MarketFilter>
                { 
                    new MarketFilter { Name = "Half Time Score", Selections = new List<SelectionFilter> {  new SelectionFilter {  Name = "0-0",  BestPrice = new BestPriceFilter { Type = PriceType.Lay,  MinOdds = 3, MaxOdds = 5 } } } },
                    new MarketFilter { Name = "Correct Score", Liquidity = new LiquidityFilter { MinMatched = 500 }, Selections = new List<SelectionFilter> { new SelectionFilter { Name = "0-0", BestPrice = new BestPriceFilter { Type = PriceType.Back, MinOdds = 10, MaxOdds = 15 } } } },
                    new MarketFilter { Name = "Over/Under 2.5 Goals", Selections = new List<SelectionFilter> { new SelectionFilter { Name = "Over 2.5 Goals", BestPrice = new BestPriceFilter { Type = PriceType.Back, MinOdds = 1, MaxOdds = 2.2 } } } }
                }
            };

            StrategyFilter u25start = new StrategyFilter
            {
                Name = "Back the under 2.5 from start",
                Markets = new List<MarketFilter>
                {
                    new MarketFilter { Name = "Over/Under 2.5 Goals", Liquidity = new LiquidityFilter { MinMatched = 1 }, Selections = new List<SelectionFilter> { new SelectionFilter { Name = "Under 2.5 Goals", BestPrice = new BestPriceFilter { Type = PriceType.Back, MinOdds = 1.75, MaxOdds = 2.1 } } } },
                    new MarketFilter { Name = "Match Odds", Liquidity = new LiquidityFilter { MinMatched = 1 }, BestSelections = new List<BestSelectionFilter> { new BestSelectionFilter { BestPrice = new BestPriceFilter { MinOdds = 1.5, Type = PriceType.Back } } } }
                }
            };

            StrategyCollection strats = new StrategyCollection();
            strats.Strategies = new List<StrategyFilter>() { showcase, ltd, ltd2, over15, over25, cs001221, cs0011, o25ht00ft00, u25start };
            return strats;
        }
    }

    [XmlType("Strategies")]
    public class StrategyCollection
    {
        [XmlElement("Strategy")]
        public List<StrategyFilter> Strategies { get; set; }
    }

    public class StrategyFilter
    {
        public StrategyFilter()
        {
            IsEnabled = true;
        }

        [XmlAttribute]
        public string Name { get; set; }
        [XmlAttribute("Enabled"), DefaultValue(true)]
        public bool IsEnabled { get; set; }
        [XmlElement("Market")]
        public List<MarketFilter> Markets { get; set; }

        public MyMarket[] Filter(IEnumerable<Tuple<MyMarket, MarketPricesCompressed>> markets)
        {
            if (IsEnabled && Markets != null)
            {
                return (from filter in Markets
                        from market in markets
                        where filter.Qualifies(market.Item1, market.Item2)
                        select market.Item1).ToArray();
            }

            return null;
        }
    }

    [XmlType("Market")]
    public class MarketFilter
    {
        [XmlAttribute, DefaultValue(0)]
        public int ID { get; set; }
        [XmlAttribute]
        public string Name { get; set; }
        [XmlElement]
        public LiquidityFilter Liquidity { get; set; }
        [XmlElement("Selection")]
        public List<SelectionFilter> Selections { get; set; }
        [XmlElement("BestSelection")]
        public List<BestSelectionFilter> BestSelections { get; set; }

        public bool Qualifies(MyMarket market, MarketPricesCompressed prices)
        {
            bool marketQualifies = (market.marketId == ID) || (market.name != null && Name != null && market.name.ToLowerInvariant() == Name.ToLowerInvariant());
            bool liquidityQualifies = Liquidity == null || Liquidity.Qualifies(market);
            bool selectionsQualify = market.runners != null && !market.runners.Any(selection => Selections == null || !Selections.Any(filter => !filter.Qualifies(selection, prices.CompletePriceInfo.FirstOrDefault(p => p.SelectionId == selection.selectionId)))); // one-liner :P
            bool bestSelectionsQualify = market.runners != null && !market.runners.Any(selection => BestSelections == null || !BestSelections.Any(filter => !filter.Qualifies(prices)));
            return marketQualifies && liquidityQualifies && selectionsQualify && bestSelectionsQualify;
        }
    }

    public class LiquidityFilter
    {
        public LiquidityFilter()
        {
            Currency = "GBP";
            ExchangeRate = 1;
            MinMatched = double.MinValue;
            MaxMatched = double.MaxValue;
        }

        [XmlAttribute, DefaultValue("GBP")]
        public string Currency { get; set; }
        [XmlAttribute, DefaultValue(1)]
        public double ExchangeRate { get; set; }
        [XmlAttribute, DefaultValue(double.MinValue)]
        public double MinMatched { get; set; }
        [XmlAttribute, DefaultValue(double.MaxValue)]
        public double MaxMatched { get; set; }

        public bool Qualifies(MyMarket market)
        {
            double totalMatchedInConvertedCurrency = market.marketTotalMatched * ExchangeRate;
            return totalMatchedInConvertedCurrency >= MinMatched && totalMatchedInConvertedCurrency <= MaxMatched;
        }
    }

    public abstract class BaseSelectionFilter
    {
        public BestPriceFilter BestPrice { get; set; }
        public NoGapsFilter NoGaps { get; set; }
        public TotalUnmatchedFilter TotalUnmatched { get; set; }

        protected bool Qualifies2(MarketPricesCompressed.SelectionPricesInfo prices)
        {
            return (BestPrice == null || BestPrice.Qualifies(prices)) &&
                    (NoGaps == null || NoGaps.Qualifies()) &&
                    (TotalUnmatched == null || TotalUnmatched.Qualifies());
        }
    }

    public class SelectionFilter : BaseSelectionFilter
    {
        [XmlAttribute, DefaultValue(0)]
        public int ID { get; set; }
        [XmlAttribute, DefaultValue(0)]
        public int Index { get; set; }
        [XmlAttribute]
        public string Name { get; set; }
        
        public bool Qualifies(Runner selection, MarketPricesCompressed.SelectionPricesInfo prices)
        {
            if ((selection.selectionId == ID) || (selection.name.ToLowerInvariant() == Name.ToLowerInvariant()) || (prices.OrderIndex == Index))
            {
                return Qualifies2(prices);
            }

            return true;
        }
    }

    public class BestSelectionFilter : BaseSelectionFilter
    {
        public BestSelectionFilter()
        {
            Index = 1;
        }

        [XmlAttribute, DefaultValue(1)]
        public int Index { get; set; }

        public bool Qualifies(MarketPricesCompressed prices)
        {
            var selection = (from s in prices.CompletePriceInfo
                             orderby s.BestPricesToBack(1, false).FirstOrDefault().Odds
                             select s).Skip(Index - 1).FirstOrDefault();

            return selection != null && Qualifies2(selection);
        }
    }

    public enum PriceType
    {
        Both,
        Back,
        Lay
    }

    public class BestPriceFilter
    {
        public BestPriceFilter()
        {
            MinOdds = double.MinValue;
            MaxOdds = double.MaxValue;
            MinUnmatched = double.MinValue;
            MaxUnmatched = double.MaxValue;
            Type = PriceType.Both;
        }

        [XmlAttribute, DefaultValue(0)]
        public PriceType Type { get; set; }
        [XmlAttribute, DefaultValue(double.MinValue)]
        public double MinOdds { get; set; }
        [XmlAttribute, DefaultValue(double.MaxValue)]
        public double MaxOdds { get; set; }
        [XmlAttribute, DefaultValue(double.MinValue)]
        public double MinUnmatched { get; set; }
        [XmlAttribute, DefaultValue(double.MaxValue)]
        public double MaxUnmatched { get; set; }

        public bool Qualifies(MarketPricesCompressed.SelectionPricesInfo prices)
        {
            var bestBack = prices.BestPricesToBack(1, false).FirstOrDefault();
            var bestLay = prices.BestPricesToLay(1, false).FirstOrDefault();

            bool bestBackQualifies = bestBack.Odds >= MinOdds && bestBack.Odds <= MaxOdds &&
                    bestBack.BackAmountAvailable >= MinUnmatched && bestBack.BackAmountAvailable <= MaxUnmatched;
            bool bestLayQualifies = bestBack.Odds >= MinOdds && bestBack.Odds <= MaxOdds &&
                    bestBack.BackAmountAvailable >= MinUnmatched && bestBack.BackAmountAvailable <= MaxUnmatched;

            switch (Type)
            {
                case PriceType.Back:
                    return bestBackQualifies;
                case PriceType.Lay:
                    return bestLayQualifies;
                case PriceType.Both:
                    return bestBackQualifies && bestLayQualifies;
            }

            return false;
        }
    }

    public class NoGapsFilter
    {
        public bool Qualifies()
        {
            return true;
        }
    }

    public class TotalUnmatchedFilter
    {
        public bool Qualifies()
        {
            return false;
        }
    }
}
